//    Copyright (C) Kherty.  All rights reserved.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.IO.Packaging;
using System.Reflection;
using OpenLS.Core.Serialization;
using OpenLS.Spreadsheet.Data;
using OpenLS.Spreadsheet.Expressions;
using OpenLS.Spreadsheet.Formats;
using OpenLS.Spreadsheet.Internals;
using OpenLS.Spreadsheet.Serialization;

namespace OpenLS.Spreadsheet
{
    public class Table : TableBase
    {


        
        
        private AutoFilter _autoFilter;
        private string _comment;
        private DataConnection _connection;
        private string _dataCellStyle;
        private DifferentialStyleRecord _dataDxfId;
        private string _displayName;
        private ExtensionElement _extension;
        private DifferentialStyleRecord _headerRowBorderDxfId;
        private string _headerRowCellStyle;
        private int _headerRowCount = 1;
        private DifferentialStyleRecord _headerRowDxfId;
        private int _id;
        private bool _insertRow;
        private bool _insertRowShift;
        private string _name;
        private bool _published;
        private QueryTable _queryTable;
        private SortState _sortState;
        private TableStyle _style;
        private DifferentialStyleRecord _tableBorderDxfId;
        private List<TableColumn> _tableColumns = new List<TableColumn>();
        private TableType _tableType;
        private DifferentialStyleRecord _totalsRowBorderDxfId;
        private string _totalsRowCellStyle;
        private int _totalsRowCount;
        private DifferentialStyleRecord _totalsRowDxfId;
        private bool _totalsRowShown = true;

        internal Table()
        {
            RowStripesVisible = true;
        }

        public Table(Range range)
        {
            RowStripesVisible = true;
            Range = range;
            int i = 1;
            foreach (Column column in Range.GetColumns())
            {
                Columns.Add(new TableColumn(i, "Column" + i));
                Cell cell = range[0, i - 1];
                var v = cell.Value as string;
                if (v == null)
                {
                    v = "Column" + i;
                    cell.Value = v;
                }
                i++;
            }
            AutoFilter = new AutoFilter(range);
        }

        public TableStyle Style
        {
            get { return _style; }
            set
            {
                if (Range == null)
                {
                    _style = value;
                    return;
                }
                Workbook workbook = Range.Worksheet.Workbook;
                using (workbook.CreateUndoScope(StringTable.TableStyle))
                {
                    if (workbook.UndoEnabled)
                        workbook.AddUndoUnit(new TableStyleUndoUnit(this, _style));
                    _style = value;
                    Range.Worksheet.OnCellBorderOrBackgroundChanged(null);
                }

                notifyPropertyChanged("Style");
            }
        }

        public QueryTable QueryTable
        {
            get { return _queryTable; }
            set
            {
                _queryTable = value;
                notifyPropertyChanged("QueryTable");
            }
        }


        public ExtensionElement Extension
        {
            get { return _extension; }
            set
            {
                _extension = value;
                notifyPropertyChanged("Extension");
            }
        }


        public AutoFilter AutoFilter
        {
            get { return _autoFilter; }
            set
            {
                _autoFilter = value;
                notifyPropertyChanged("AutoFilter");
            }
        }

        public SortState SortState
        {
            get { return _sortState; }
            set
            {
                _sortState = value;
                notifyPropertyChanged("SortState");
            }
        }

        public List<TableColumn> Columns
        {
            get { return _tableColumns; }
            set
            {
                _tableColumns = value;
                notifyPropertyChanged("Columns");
            }
        }

        private TableStyleInfo tableStyleInfo;

        public TableStyleInfo StyleInfo
        {
            get { return tableStyleInfo; }
            set { tableStyleInfo = value; notifyPropertyChanged("StyleInfo");}
        }
        [Obsolete("use FirstColumnVisible insead")]
        public bool ShowFirstColumn { get { return FirstColumnVisible; } set { FirstColumnVisible = value; } }
        public bool FirstColumnVisible { get; set; }

        [Obsolete("Use LastColumnVisible instead")]
        public bool ShowLastColumn { get { return LastColumnVisible; } set { LastColumnVisible = value; } }
        public bool LastColumnVisible { get; set; }

        [Obsolete("Use RowStripesVisible instead")]
        public bool ShowRowStripes { get { return RowStripesVisible; } set { RowStripesVisible = value; } }
        public bool RowStripesVisible { get; set; }

        [Obsolete("use ColumnStripesVisible instead")]
        public bool ShowColumnStripes { get { return ColumnStripesVisible; } set { ColumnStripesVisible = value; } }
        public bool ColumnStripesVisible { get; set; }

        internal int Id
        {
            get { return _id; }
            set { _id = value; }
        }

        public string Name
        {
            get
            {
                if (_name == null)
                    _name = Guid.NewGuid().ToString().Replace("-", "");
                return _name;
            }
            set
            {
                _name = value;
                notifyPropertyChanged("Name");
            }
        }

        public string DisplayName
        {
            get
            {
                if (string.IsNullOrEmpty(_displayName))
                    _displayName = "Table" + _id;
                return _displayName;
            }
            set
            {
                _displayName = value;
                notifyPropertyChanged("DisplayName");
            }
        }

        public JsonDataTableFormat JsonTableDefinition
        {
            get; set;
        }


        public string Comment
        {
            get { return _comment; }
            set
            {
                _comment = value;
                notifyPropertyChanged("Comment");
            }
        }
        internal SimpleRange SimpleRange{get{ return (SimpleRange) this.Range;}}

        public Range RangeWithoutHeader
        {
            get
            {
                if (HeaderRowCount == 0)
                    return Range;
                if (Range is FullColumnRange)
                    return Range;
                if (Range is FullWorksheetRange)
                    return Range;
                if (Range is FullRowRange)
                {
                    var frr = (FullRowRange) Range;
                    return new FullRowRange(frr.Worksheet.Rows[frr.FirstRow.Index + HeaderRowCount],
                                            frr.LastRow);
                }
                Cell c1 = Range.TopLeftCell;
                return new MultiCellsRange(Range.Worksheet[c1.Row.Index + HeaderRowCount, c1.Column.Index],
                                           Range.Worksheet[
                                               c1.Row.Index + Range.RowSpan - 1, c1.Column.Index + Range.ColumnSpan - 1]);
            }
        }

        public TableType Type
        {
            get { return _tableType; }
            set
            {
                _tableType = value;
                notifyPropertyChanged("Type");
            }
        }

        public int HeaderRowCount
        {
            get { return _headerRowCount; }
            set
            {
                _headerRowCount = value;
                notifyPropertyChanged("HeaderRowCount");
            }
        }

        public bool InsertRow
        {
            get { return _insertRow; }
            set
            {
                _insertRow = value;
                notifyPropertyChanged("InsertRow");
            }
        }

        public bool InsertRowShift
        {
            get { return _insertRowShift; }
            set
            {
                _insertRowShift = value;
                notifyPropertyChanged("InsertRowShift");
            }
        }

        public int TotalsRowCount
        {
            get { return _totalsRowCount; }
            set
            {
                _totalsRowCount = value;
                notifyPropertyChanged("TotalsRowCount");
            }
        }

        [Obsolete("use TotolsRowVisible insead")]
        public bool TotalsRowShown
        {
            get { return _totalsRowShown; }
            set
            {
                _totalsRowShown = value;
                notifyPropertyChanged("TotalsRowShown");
                notifyPropertyChanged("TotalsRowVisible");
            }
        }
        public bool TotalsRowVisible
        {
            get { return _totalsRowShown; }
            set
            {
                _totalsRowShown = value;
                notifyPropertyChanged("TotalsRowShown");
                notifyPropertyChanged("TotalsRowVisible");
            }
        }

        public bool Published
        {
            get { return _published; }
            set
            {
                _published = value;
                notifyPropertyChanged("Published");
            }
        }

        internal DifferentialStyleRecord HeaderRowDifferentialStyleRecord
        {
            get { return _headerRowDxfId; }
            set
            {
                _headerRowDxfId = value;
                notifyPropertyChanged("HeaderRowDifferentialStyleRecord");
            }
        }

        internal DifferentialStyleRecord DataDifferentialStyleRecord
        {
            get { return _dataDxfId; }
            set
            {
                _dataDxfId = value;
                notifyPropertyChanged("DataDifferentialStyleRecord");
            }
        }

        internal DifferentialStyleRecord TotalsRowDataDifferentialStyleRecord
        {
            get { return _totalsRowDxfId; }
            set
            {
                _totalsRowDxfId = value;
                notifyPropertyChanged("TotalsRowDataDifferentialStyleRecord");
            }
        }

        internal DifferentialStyleRecord HeaderRowBorderDataDifferentialStyleRecord
        {
            get { return _headerRowBorderDxfId; }
            set
            {
                _headerRowBorderDxfId = value;
                notifyPropertyChanged("HeaderRowBorderDataDifferentialStyleRecord");
            }
        }

        internal DifferentialStyleRecord TableBorderDataDifferentialStyleRecord
        {
            get { return _tableBorderDxfId; }
            set
            {
                _tableBorderDxfId = value;
                notifyPropertyChanged("TableBorderDataDifferentialStyleRecord");
            }
        }

        internal DifferentialStyleRecord TotalsRowBorderDataDifferentialStyleRecord
        {
            get { return _totalsRowBorderDxfId; }
            set
            {
                _totalsRowBorderDxfId = value;
                notifyPropertyChanged("TotalsRowborderDataDifferentialStyleRecord");
            }
        }

        public string HeaderRowCellStyle
        {
            get { return _headerRowCellStyle; }
            set
            {
                _headerRowCellStyle = value;
                notifyPropertyChanged("HeaderRowCellStyle");
            }
        }

        public string DataCellStyle
        {
            get { return _dataCellStyle; }
            set
            {
                _dataCellStyle = value;
                notifyPropertyChanged("DataCellStyle");
            }
        }

        public string TotalsRowCellStyle
        {
            get { return _totalsRowCellStyle; }
            set
            {
                _totalsRowCellStyle = value;
                notifyPropertyChanged("TotalsRowCellStyle");
            }
        }

        public DataConnection Connection
        {
            get { return _connection; }
            set
            {
                _connection = value;
                notifyPropertyChanged("Connection");
            }
        }

        #region Table rights management

        public bool IsLocal { get; set; }

        public bool IsPublic { get; set; }

        public bool IsReadOnly { get; set; }

        public bool AllowsInsert { get; set; }

        public bool AllowsUpdate { get; set; }

        public bool AllowsDelete { get; set; }

        #endregion



        #region INotifyPropertyChanged Members

//        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        #region IOfficeXmlSerializable Members

        internal override void ReadXml(ReadContext context)
        {
            using (ReadContext c = context.Read("table"))
            {
                Id = c.GetInteger("id");
                Name = c.GetOptionalString("name");
                DisplayName = c.GetString("displayName");
                Comment = c.GetOptionalString("comment");
                var ws = c.GetData<Worksheet>();
                Range = ws.GetRange(c.GetString("ref"));
                Type = c.GetOptionalEnum("tableType", TableType.Worksheet);
                HeaderRowCount = c.GetOptionalInteger("headerRowCount", 1);
                InsertRow = c.GetOptionalBoolean("insertRow", false);
                InsertRowShift = c.GetOptionalBoolean("insertRowShift", false);
                TotalsRowCount = c.GetOptionalInteger("totalsRowCount", 0);
                TotalsRowVisible = c.GetOptionalBoolean("totalsRowShown", true);
                Published = c.GetOptionalBoolean("published", false);
                _headerRowDxfId = getDifferentialStyleRecord(c, "headerRowDxfId");
                _dataDxfId = getDifferentialStyleRecord(c, "dataDxfId");
                _headerRowBorderDxfId = getDifferentialStyleRecord(c, "headerRowBorderDxfId");
                _tableBorderDxfId = getDifferentialStyleRecord(c, "tableBorderDxfId");
                _totalsRowBorderDxfId = getDifferentialStyleRecord(c, "totalsRowBorderDxfId");
                HeaderRowCellStyle = c.GetOptionalString("headerRowCellStyle");
                DataCellStyle = c.GetOptionalString("dataCellStyle");
                TotalsRowCellStyle = c.GetOptionalString("totalsRowCellStyle");
                int? connectionId = c.GetOptionalUIntegerAsInteger("connectionId");
                Connection = connectionId != null ? c.GetData<Workbook>().GetConnectionById(connectionId.Value) : null;
                AutoFilter = c.ReadOptionalElement<AutoFilter>("autoFilter");
                SortState = c.ReadOptionalElement<SortState>("sortState");
                using (ReadContext c2 = c.Read("tableColumns"))
                {
                    Columns.Clear();
                    while (c2.ReaderLocalName == "tableColumn")
                        Columns.Add(c2.ReadElement<TableColumn>());
                }
                var styleInfo = c.ReadOptionalElement<TableStyleInfo>("tableStyleInfo");
                if (styleInfo != null && styleInfo.Name != null)
                {
                    var wb = context.GetData<Workbook>();
                    Style = wb.GetStyle(styleInfo.Name);
                    styleInfo.CopyTo(this);
                }
                else
                    Style = null;
                Extension = c.ReadOptionalElement<ExtensionElement>("extLst");
            }
            readQueryTable(context);
        }

        internal override void WriteXml(WriteContext context)
        {
            using (WriteContext c = context.Write("table"))
            {
                c.SetInteger("id", Id);
                c.SetOptionalString("name", Name);
                c.SetString("displayName", DisplayName);
                c.SetOptionalString("comment", Comment);
                c.SetString("ref", Range.Label);
                c.SetOptionalEnum("tableType", Type, TableType.Worksheet);
                c.SetOptionalInteger("headerRowCount", HeaderRowCount, 1);
                c.SetOptionalBoolean("insertRow", InsertRow, false);
                c.SetOptionalBoolean("insertRowShift", InsertRowShift, false);
                c.SetOptionalInteger("totalsRowCount", TotalsRowCount, 0);
                c.SetOptionalBoolean("totalsRowShown", TotalsRowVisible, true);
                c.SetOptionalBoolean("published", Published, false);
                setOptionalDifferentialStyleRecord(c, HeaderRowDifferentialStyleRecord, "headerRowDxfId");
                setOptionalDifferentialStyleRecord(c, DataDifferentialStyleRecord, "dataDxfId");
                setOptionalDifferentialStyleRecord(c, HeaderRowBorderDataDifferentialStyleRecord, "headerRowBorderDxfId");
                setOptionalDifferentialStyleRecord(c, TableBorderDataDifferentialStyleRecord, "tableBorderDxfId");
                setOptionalDifferentialStyleRecord(c, TotalsRowBorderDataDifferentialStyleRecord, "totalsRowBorderDxfId");
                c.SetOptionalString("headerRowCellStyle", HeaderRowCellStyle);
                c.SetOptionalString("dataCellStyle", DataCellStyle);
                c.SetOptionalString("totalsRowCellStyle", TotalsRowCellStyle);
                if (Connection != null)
                    c.SetUIntegerAsInteger("connectionId", Connection.Id); //\\
                c.WriteOptionalElement(AutoFilter);
                c.WriteOptionalElement(SortState);
                using (WriteContext c2 = c.Write("tableColumns"))
                {
                    c2.SetInteger("count", Columns.Count);
                    int i = 0;
                    foreach (TableColumn column in Columns)
                    {
                        if (column.Name == null)
                        {
                            object val = Range[0, i].Value ?? Guid.NewGuid().ToString().Replace("-", "");
                            column.Name = val.ToString();
                        }
                        i++;
                        column.Id = i;
                        c2.WriteElement(column);
                    }
                }
                if (Style != null)
                {
                    var styleInfo = new TableStyleInfo(this);
                    c.WriteOptionalElement(styleInfo);
                }
                else
                    c.WriteOptionalElement(Extension);
            }
            writeQueryTable(context);
        }

        #endregion

        private void readQueryTable(ReadContext context)
        {
            foreach (
                PackageRelationship rel in
                    context.Part.GetRelationshipsByType(SpreadsheetConstants.QueryTableRelationshipType))
            {
                using (ReadContext c = context.OpenRelatedContext(rel, SpreadsheetConstants.SpreadsheetNamespaceUri))
                {
                    c.AddData(this);
                    QueryTable = c.ReadElement<QueryTable>();
                }
            }
        }

        private static DifferentialStyleRecord getDifferentialStyleRecord(ReadContext c, string name)
        {
            int? v = c.GetOptionalInteger(name);
            if (v == null)
                return null;

            var l = c.GetData<DifferentialStyleRecordResolver>();
            return l[v.Value];
        }

        private void writeQueryTable(WriteContext context)
        {
            if (QueryTable == null)
                return;
            using (WriteContext c = context.CreateRelatedContext(null, SpreadsheetConstants.SpreadsheetNamespaceUri,
                                                                 context.GetAvailableUri(
                                                                     "/xl/queryTables/queryTable{0}.xml"),
                                                                 SpreadsheetConstants.QueryTableRelationshipType,
                                                                 SpreadsheetConstants.QueryTableContentType))
            {
                c.WriteElement(QueryTable);
            }
        }

        private static void setOptionalDifferentialStyleRecord(WriteContext c, DifferentialStyleRecord r, string name)
        {
            if (r == null)
                return;
            var dic = c.GetData<WriteObjectTable<DifferentialStyleRecord>>();
            c.SetInteger(name, dic[r]);
        }

        protected override void hookRowColumns()
        {
            Range.TopLeftCell.Column.Deleted += firstColumnDeleted;
            Range.TopLeftCell.Row.Deleted += firstRowDeleted;
            if (Range is MultiCellsRange)
            {
                var r = (MultiCellsRange) Range;
                r.BottomRight.Column.Deleted += lastColumnDeleted;
                r.BottomRight.Row.Deleted += lastRowDeleted;
            }
            if (Range is FullColumnRange)
            {
                ((FullColumnRange) Range).LastColumn.Deleted += lastColumnDeleted;
            }
            if (Range is FullRowRange)
            {
                ((FullRowRange) Range).LastRow.Deleted += lastRowDeleted;
            }
        }

        protected override void unhookRowColumns()
        {
            Range.TopLeftCell.Column.Deleted -= firstColumnDeleted;
            Range.TopLeftCell.Row.Deleted -= firstRowDeleted;
            if (Range is MultiCellsRange)
            {
                var r = (MultiCellsRange) Range;
                r.BottomRight.Column.Deleted -= lastColumnDeleted;
                r.BottomRight.Row.Deleted -= lastRowDeleted;
            }
            if (Range is FullColumnRange)
            {
                ((FullColumnRange) Range).LastColumn.Deleted -= lastColumnDeleted;
            }
            if (Range is FullRowRange)
            {
                ((FullRowRange) Range).LastRow.Deleted -= lastRowDeleted;
            }
        }

        private void lastRowDeleted(object sender, EventArgs e)
        {
            var row = (Row) sender;
            Row firstRow = Range.TopLeftCell.Row;
            if (row == firstRow)
            {
                deleteSelf();
                return;
            }
            RangeAddress address = Range.Address;
            if (Range is CellsRange)
            {
                address = new RangeAddress(address.TopLeft, address.BottomRight.GetNeighbor(NavigationDirection.Up));
                Range = CellsRange.Create(Range.Worksheet, address);
            }
            else if (Range is FullRowRange)
            {
                var frr = (FullRowRange) Range;
                Range = new FullRowRange(frr.FirstRow, frr.LastRow.Worksheet.Rows[frr.LastRow.Index - 1]);
            }
        }

        private void deleteSelf()
        {
            Range.Worksheet.RemoveTable(this);
        }

        private void lastColumnDeleted(object sender, EventArgs e)
        {
            var column = (Column) sender;
            Column firstColumn = Range.TopLeftCell.Column;
            if (column == firstColumn)
            {
                deleteSelf();
                return;
            }
            RangeAddress address = Range.Address;
            if (Range is CellsRange)
            {
                address = new RangeAddress(address.TopLeft, address.BottomRight.GetNeighbor(NavigationDirection.Left));
                Range = CellsRange.Create(Range.Worksheet, address);
            }
            else if (Range is FullColumnRange)
            {
                var frr = (FullColumnRange) Range;
                Range = new FullColumnRange(frr.FirstColumn, frr.LastColumn.Worksheet.Columns[frr.LastColumn.Index - 1]);
            }
        }

        private void firstRowDeleted(object sender, EventArgs e)
        {
            var row = (Row) sender;
            Row lastRow = Range.TopLeftCell.Row; //\\
            if (row == lastRow)
            {
                deleteSelf();
                return;
            }
            RangeAddress address = Range.Address;
            if (Range is CellsRange)
            {
                address = new RangeAddress(address.TopLeft.GetNeighbor(NavigationDirection.Down), address.BottomRight);
                Range = CellsRange.Create(Range.Worksheet, address);
            }
            else if (Range is FullRowRange)
            {
                var frr = (FullRowRange) Range;
                Range = new FullRowRange(frr.FirstRow.Worksheet.Rows[frr.FirstRow.Index + 1], frr.FirstRow);
            }
        }

        private void firstColumnDeleted(object sender, EventArgs e)
        {
            var column = (Column) sender;
            Column firstColumn = Range.TopLeftCell.Column;
            if (column == firstColumn)
            {
                deleteSelf();
                return;
            }
            RangeAddress address = Range.Address;
            if (Range is CellsRange)
            {
                address = new RangeAddress(address.TopLeft.GetNeighbor(NavigationDirection.Right), address.BottomRight);
                Range = CellsRange.Create(Range.Worksheet, address);
            }
            else if (Range is FullColumnRange)
            {
                var frr = (FullColumnRange) Range;
                Range = new FullColumnRange(frr.FirstColumn.Worksheet.Columns[frr.FirstColumn.Index + 1], frr.LastColumn);
            }
        }

        internal DifferentialStyleRecord GetDifferentialStyleFor(Cell cell, Predicate<DifferentialStyleRecord> predicate)
        {
            TableStyleElement e = GetElementFor(cell, predicate);
            if (e != null)
                return e.StyleRecord;
            return null;
        }

        private TableStyleElement GetElementFor(Cell cell, Predicate<DifferentialStyleRecord> predicate)
        {
            var i = new TableLayoutInfo(Style, Range.Address, FirstColumnVisible, LastColumnVisible, HeaderRowCount,
                                        TotalsRowCount,
                                        RowStripesVisible, ColumnStripesVisible);
            return i.GetElementFor(cell.Address, predicate);
        }

        internal bool IsHeader(Cell cell)
        {
            int diff = cell.Row.Index - Range.TopLeftCell.Row.Index;
            return diff < HeaderRowCount;
        }

#if DEBUG
        internal bool IsDeleted;
        [ContractInvariantMethod]
        private void ObjectInvariant()
        {
            Contract.Invariant(IsDeleted || inSetup || Range == null || Enumerable.All(Range.GetNonEmptyCells(), cell => cell.Table == this));
           /* foreach (Cell cell in Range.GetNonEmptyCells())
            {
                Contract.Assert(cell.Table == this);
            }*/
        }
#endif
    }
}